import os
import subprocess
import time
import re
import requests
import json
from pptx import Presentation
from pptx.enum.shapes import MSO_SHAPE_TYPE
from pptx.util import Inches
from PIL import Image
from pydub import AudioSegment
from tencentcloud.common import credential
from tencentcloud.common.profile.client_profile import ClientProfile
from tencentcloud.common.profile.http_profile import HttpProfile
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
from tencentcloud.tts.v20190823 import tts_client, models


# ---------------------- 配置参数 ----------------------
TENCENT_SECRET_ID = "AKIDlW7VgfmfLo6DAyeFestgVDmDUaUSIvLb"
TENCENT_SECRET_KEY = "DE1f9PWfdAXjngNqCMNL5czDE5mJgwBP"
TENCENT_REGION = "ap-guangzhou"
TTS_VOICE_TYPE = 101001
TTS_SPEED = 0
TTS_VOLUME = 5
DEEPSEEK_API_URL = "https://api.deepseek.com/chat/completions"
DEEPSEEK_API_KEY = "sk-4075b9d050be4a89a5c0f04f066dc2d6"
TEMP_DIR = "./temp_edu_res"  # 基础临时目录
SUBTITLE_FONT = "SimHei"
SUBTITLE_SIZE = 18
SUBTITLE_COLOR = "red"


# ---------------------- 核心修复：PPT幻灯片提取 ----------------------
def extract_ppt_slides_with_animations(pptx_path: str) -> list:
    """提取PPT所有幻灯片并返回相关信息（修复路径和导出逻辑）"""
    # 确保基础临时目录存在（使用正确的路径拼接）
    os.makedirs(TEMP_DIR, exist_ok=True)
    slide_info = []
    slide_text_dic = extract_ppt_text(pptx_path)
    actual_slide_count = len(slide_text_dic)  # 获取PPT实际页数
    print(f"检测到PPT总页数：{actual_slide_count}页")    
    try:
        # 1. 从ppt中提取每张幻灯片为图片png
        prs = Presentation(pptx_path)
        # Choose slide number
        slide = prs.slides[0]

        # Save slides
        for index, shape in enumerate(slide.shapes):
            if shape.shape_type == MSO_SHAPE_TYPE.PICTURE:
                image = shape.image
                image_bytes = image.blob
                with open(f'slide_image_{index}.png', 'wb') as image_file:
                    image_file.write(image_bytes)

        # 3. 列出导出的所有文件（用于调试）
        all_exported_files = os.listdir(TEMP_DIR)
        print(f"导出目录下的所有文件：{all_exported_files}")

        # 4. 筛选PNG文件（放宽匹配条件，避免因命名差异漏选）
        pptx_filename = os.path.splitext(os.path.basename(pptx_path))[0]
        exported_files = []
        for f in all_exported_files:
            # 只要是PNG文件就纳入（避免因命名不匹配导致漏选）
            if f.lower().endswith(".png"):
                exported_files.append(f)

        # 检查是否有导出文件
        if not exported_files:
            raise RuntimeError("未找到导出的PNG图片，请检查LibreOffice是否正常工作")
        print(f"筛选出的PNG文件：{exported_files}")

        # 5. 增强排序逻辑：兼容更多命名格式
        def get_slide_number(filename):
            # 尝试从文件名提取数字（支持多种格式）
            numbers = re.findall(r'\d+', filename)  # 提取所有数字
            if numbers:
                return int(numbers[0])  # 返回第一个数字作为页码
            return 0  # 无数字时视为第0页（会被排在前面）

        # 按页码排序
        exported_files.sort(key=get_slide_number)
        print(f"排序后的文件：{exported_files}")

        # 6. 验证导出数量（不匹配时警告但继续执行）
        if len(exported_files) != actual_slide_count:
            print(f"警告：导出的幻灯片数量({len(exported_files)})与PPT实际页数({actual_slide_count})不符")

        # 7. 处理导出文件并关联文本
        for slide_idx, filename in enumerate(exported_files):
            src_path = os.path.join(TEMP_DIR, filename).replace('\\','/')
            # 修复目标路径：移除前导斜杠
            dst_path = os.path.join(TEMP_DIR, f"slide_{slide_idx}.png").replace('\\','/')
            # 确保源文件存在再重命名
            if not os.path.exists(src_path):
                print(f"警告：源文件不存在 {src_path}，跳过该页")
                continue
            os.rename(src_path, dst_path)
            # 获取文本信息
            raw_text = slide_text_dic.get(slide_idx, f"第{slide_idx+1}页内容")
            optimized_text = optimize_text_with_deepseek(raw_text)
            # 估算时长
            word_count = len(optimized_text.replace(" ", "").replace("\n", ""))
            estimated_duration = word_count * 0.3
            slide_info.append((dst_path, estimated_duration, optimized_text))
            print(f"已处理幻灯片 {slide_idx+1}/{len(exported_files)}：{dst_path}")
    except Exception as E:
        print('导出图片错误：', E)
    return slide_info

# ---------------------- 其他函数修复：路径处理 ----------------------
def extract_ppt_text(pptx_path: str) -> dict:
    """提取每张PPT的正文+备注文本"""
    prs = Presentation(pptx_path)
    slide_text_list = {}
    for slide_idx, slide in enumerate(prs.slides):
        body_text = []
        for shape in slide.shapes:
            if hasattr(shape, "text") and shape.text.strip():
                body_text.append(shape.text.strip())
        note_text = slide.notes_slide.notes_text_frame.text             
        merged_text = "\n".join(list(set(body_text + [note_text]))) if (body_text or note_text) else f"第{slide_idx+1}页内容"
        slide_text_list[slide_idx] = merged_text
    print(f"成功提取 {len(slide_text_list)} 页PPT文本")
    return slide_text_list


def generate_slide_video_with_tts_subtitle(slide_path: str, optimized_text: str, slide_idx: int) -> tuple:
    # 修复路径：移除前导斜杠
    audio_path = os.path.join(TEMP_DIR, f"audio_{slide_idx}.mp3").replace('\\','/')
    srt_path = os.path.join(TEMP_DIR, f"subtitle_{slide_idx}.srt").replace('\\','/')
    slide_video_path = os.path.join(TEMP_DIR, f"slide_video_{slide_idx}.mp4").replace('\\','/')
    
    print(f"正在为幻灯片 {slide_idx+1} 生成音频和字幕...")
    word_count = tencent_tts_with_timestamp(optimized_text, audio_path, srt_path)
    
    print(f"正在为幻灯片 {slide_idx+1} 合成视频...")
    audio_duration = get_duration_pydub(audio_path)
    cmd = [
        "ffmpeg", "-y", "-loop", "1",
        "-i", slide_path, "-i", audio_path,
        "-vf", f"subtitles={srt_path}:fontsdir=./:force_style='FontName={SUBTITLE_FONT},FontSize={SUBTITLE_SIZE},PrimaryColour=&HFFFFFF&,Align=2,MarginV=50'",
        "-c:v", "libx264", "-c:a", "aac",
        "-t", str(audio_duration), "-fps_mode", "vfr", "-shortest",
        slide_video_path
    ]
    result = subprocess.run(cmd, capture_output=True, text=True)
    if result.returncode != 0:
        raise RuntimeError(f"幻灯片 {slide_idx+1} 视频合成失败：{result.stderr}")
    
    print(f"幻灯片 {slide_idx+1} 视频合成完成：{slide_video_path}")
    return slide_video_path, audio_path, srt_path


def merge_all_without_digital_human(slide_info: list, final_video_path: str):
    """合并所有幻灯片视频"""
    slide_video_paths = []
    temp_files = []
    
    # 生成所有单页视频
    for slide_idx, (slide_path, duration, optimized_text) in enumerate(slide_info):
        video_path, audio_path, srt_path = generate_slide_video_with_tts_subtitle(slide_path, optimized_text, slide_idx)
        slide_video_paths.append(video_path)
        temp_files.extend([audio_path, srt_path])
    
    if not slide_video_paths:
        raise RuntimeError("没有生成任何幻灯片视频，无法合并")
    
    # 修复输入列表路径
    input_list_path = os.path.join(TEMP_DIR, "input_list.txt").replace('\\','/')
    with open(input_list_path, "w", encoding="utf-8") as f:
        for video_path in slide_video_paths:
            f.write(f"file '{os.path.abspath(video_path)}'\n")
    
    # 合并所有视频
    print(f"正在合并 {len(slide_video_paths)} 个幻灯片视频...")
    cmd_merge = [
        "ffmpeg", "-y", "-f", "concat", "-safe", "0", "-i", input_list_path,
        "-c", "copy", final_video_path
    ]
    result = subprocess.run(cmd_merge, capture_output=True, text=True)
    if result.returncode != 0:
        raise RuntimeError(f"视频合并失败：{result.stderr}")
    
    # 清理临时文件
    for file in temp_files + slide_video_paths + [input_list_path]:
        if os.path.exists(file):
            try:
                os.remove(file)
            except Exception as e:
                print(f"清理文件失败 {file}：{e}")
    
    print(f"所有视频合并完成：{final_video_path}")


def full_education_video_pipeline(pptx_path: str, final_video_path: str):
    try:
        print("第一步：提取PPT内容和渲染幻灯片...")
        slide_info = extract_ppt_slides_with_animations(pptx_path)
        print(f"成功提取 {len(slide_info)} 页幻灯片")        
        if not slide_info:
            raise RuntimeError("未提取到任何幻灯片信息，无法继续")        
        print("第二步：合成最终教育视频...")
        merge_all_without_digital_human(slide_info, final_video_path)        
        print(f"全流程完成！最终视频：{final_video_path}")
    except Exception as e:
        print(f"流程失败：{str(e)}")


# ---------------------- 其他函数保持不变 ----------------------
def optimize_text_with_deepseek(raw_text: str) -> str:
    headers = {
        "Content-Type": "application/json",
        "Authorization": f"Bearer {DEEPSEEK_API_KEY}"
    }
    payload = {
        "model": "deepseek-chat",
        "messages": [
            {"role": "system", "content": "你是教育领域助手，将PPT文本优化为自然、流畅的讲解语言，保留核心知识点，适配口头讲解场景，语言必须要简洁"},
            {"role": "user", "content": raw_text}
        ],
        "temperature": 0.3
    }
    try:
        response = requests.post(DEEPSEEK_API_URL, headers=headers, json=payload, timeout=30)
        response.raise_for_status()
        return response.json()["choices"][0]["message"]["content"].strip()
    except Exception as e:
        raise RuntimeError(f"DeepSeek文本优化失败：{str(e)}")


def tencent_tts_with_timestamp(optimized_text: str, audio_path: str, srt_path: str):
    cred = credential.Credential(TENCENT_SECRET_ID, TENCENT_SECRET_KEY)
    httpProfile = HttpProfile()
    httpProfile.endpoint = "tts.tencentcloudapi.com"
    clientProfile = ClientProfile()
    clientProfile.httpProfile = httpProfile
    client = tts_client.TtsClient(cred, "", clientProfile)
    req = models.CreateTtsTaskRequest()
    params = {
        "Text": optimized_text,
        'VoiceType': TTS_VOICE_TYPE,
        'Speed': TTS_SPEED,
        'Volume': TTS_VOLUME,
        'Codec': 'mp3',
        'SampleRate': 16000,
        'EnableSubtitle': True
    }
    req.from_json_string(json.dumps(params))
    resp = client.CreateTtsTask(req)
    resp_json = json.loads(resp.to_json_string())
    taskID = resp_json['Data']['TaskId']
    task_flag = False
    count = 1
    while count < 50:
        req_task = models.DescribeTtsTaskStatusRequest()
        req_task.from_json_string(json.dumps({"TaskId": taskID}))
        resp_task = client.DescribeTtsTaskStatus(req_task)  
        resp_task_json = json.loads(resp_task.to_json_string())
        if resp_task_json['Data']['Status'] == 0:
            time.sleep(3)
            count += 1
        elif resp_task_json['Data']['Status'] == 2:
            task_flag = True
            break
    if not task_flag:
        raise RuntimeError(f"腾讯云TTS失败,提取mp3失败！")
    ResultUrl = resp_task_json['Data']['ResultUrl']
    Subtitles_list = resp_task_json['Data']['Subtitles']
    pattern = r'https://.*?\.mp3'
    match_result = re.search(pattern, ResultUrl)
    if not match_result:
        raise RuntimeError(f"腾讯云TTS失败,提取mp3失败！")
    audio_url = match_result.group()
    try:
        with requests.get(audio_url, stream=True) as response:
            response.raise_for_status()            
            with open(audio_path, 'wb') as f:
                for chunk in response.iter_content(chunk_size=8192):
                    if chunk:
                        f.write(chunk)
        
        # 生成句子级字幕
        if not Subtitles_list:
            return 0
        sentences = []
        current_sentence = []
        for sub in Subtitles_list:
            current_sentence.append(sub)
            if sub['Text'] in ['.', '。', ',', '，', '!', '！', '?', '？', ';', '；']:
                sentences.append(current_sentence)
                current_sentence = []
        if current_sentence:
            sentences.append(current_sentence)
        
        with open(srt_path, "w", encoding="utf-8") as f:
            for idx, sentence in enumerate(sentences, 1):
                if not sentence:
                    continue
                start_time = sentence[0]["BeginTime"]
                end_time = sentence[-1]["EndTime"]
                start = start_time // 1000
                end = end_time // 1000
                start_str = f"{start//3600:02d}:{(start%3600)//60:02d}:{start%60:02d},{(start_time%1000):03d}"
                end_str = f"{end//3600:02d}:{(end%3600)//60:02d}:{end%60:02d},{(end_time%1000):03d}"
                sentence_text = ''.join([sub['Text'] for sub in sentence])
                f.write(f"{idx}\n{start_str} --> {end_str}\n{sentence_text}\n\n")
        
        print(f"音频生成：{audio_path}，字幕生成：{srt_path}")
        return len(Subtitles_list)
    except Exception as e:
        raise RuntimeError(f"腾讯云TTS失败：{str(e)}")


def get_duration_pydub(audio_path: str) -> float:
    audio_file = AudioSegment.from_file(audio_path)
    return audio_file.duration_seconds


if __name__ == "__main__":
    full_education_video_pipeline(
        pptx_path="./input.pptx",  # 替换为你的PPT路径
        final_video_path="./final_edu_video.mp4"
    )